// define this macro to enable fast behavior simulation
// for flash by skipping SPI transfers
// `define FAST_FLASH

module spi_top_apb #(
  parameter flash_addr_start = 32'h30000000,
  parameter flash_addr_end   = 32'h3fffffff,
  parameter spi_ss_num       = 8
) (
  input         clock,
  input         reset,
  input  [31:0] in_paddr,
  input         in_psel,
  input         in_penable,
  input  [2:0]  in_pprot,
  input         in_pwrite,
  input  [31:0] in_pwdata,
  input  [3:0]  in_pstrb,
  output reg    in_pready,
  output reg [31:0]  in_prdata,
  output reg    in_pslverr,

  output                  spi_sck,
  output [spi_ss_num-1:0] spi_ss,
  output                  spi_mosi,
  input                   spi_miso,
  output                  spi_irq_out
);

`ifdef FAST_FLASH

wire [31:0] data;
parameter invalid_cmd = 8'h0;
flash_cmd flash_cmd_i(
  .clock(clock),
  .valid(in_psel && !in_penable),
  .cmd(in_pwrite ? invalid_cmd : 8'h03),
  .addr({8'b0, in_paddr[23:2], 2'b0}),
  .data(data)
);
assign spi_sck    = 1'b0;
assign spi_ss     = 8'b0;
assign spi_mosi   = 1'b1;
assign spi_irq_out= 1'b0;
assign in_pslverr = 1'b0;
assign in_pready  = in_penable && in_psel && !in_pwrite;
assign in_prdata  = data[31:0];

`else

  wire        wb_ack_o; // ready
  wire [31:0] wb_dat_o; // output data
  wire        wb_err_o; // error
  wire        wb_int_o;

  reg         wb_stb_i; // valid
  reg  [4:0]  wb_addr_i; // address
  reg  [31:0] wb_dat_i; // input data
  reg  [3:0]  wb_sel_i; // mask
  reg         wb_we_i; // read or write
  reg         wb_cyc_i; // enable wb

  reg         stb_i; // valid
  reg  [4:0]  addr_i; // address
  reg  [31:0] dat_i; // input data
  reg  [3:0]  sel_i; // mask
  reg         we_i; // read or write
  reg         cyc_i; // enable wb

  typedef enum [3:0] {idel, clear_ss, set_divider, set_ss, set_CHAR_LEN, set_addr, set_start, wait_end, read_data} state_t;
  state_t curr_state;
  state_t next_state;

  always @(posedge clock or posedge reset) begin
    if (reset) curr_state <= idel;
    else curr_state <= next_state;
  end

  always @(*) begin
    case (curr_state)
      idel: begin
        if (in_paddr >= flash_addr_start && in_paddr <= flash_addr_end && !in_pwrite && in_psel) next_state = clear_ss;
        else next_state = idel;
      end

      clear_ss: begin
        if (wb_stb_i && wb_ack_o) next_state = set_divider;
        else next_state = clear_ss;
      end

      set_divider: begin
        if (wb_stb_i && wb_ack_o) next_state = set_ss;
        else next_state = set_divider;
      end

      set_ss: begin
        if (wb_stb_i && wb_ack_o) next_state = set_CHAR_LEN;
        else next_state = set_ss;
      end

      set_CHAR_LEN: begin
        if (wb_stb_i && wb_ack_o) next_state = set_addr;
        else next_state = set_CHAR_LEN;
      end

      set_addr: begin
        if (wb_stb_i && wb_ack_o) next_state = set_start;
        else next_state = set_addr;
      end

      set_start: begin
        if (wb_stb_i && wb_ack_o) next_state = wait_end;
        else next_state = set_start;
      end

      wait_end: begin
        if (spi_irq_out) begin
          next_state = read_data;
        end
        else next_state = wait_end;
      end

      read_data: begin
        if (wb_stb_i && wb_ack_o) next_state = idel;
        else next_state = read_data;
      end

      default: begin
          next_state = idel;
          $fwrite(32'h80000002, "Assertion failed: error state when reading flash\n");
          $fatal;
      end
    endcase
  end

  always @(posedge clock or posedge reset) begin
    if (reset) begin
      wb_stb_i  <= 1'b0;
      wb_addr_i <= 5'd0;
      wb_dat_i  <= 32'd0;
      wb_sel_i  <= 4'd0;
      wb_we_i   <= 1'b0;
      wb_cyc_i  <= 1'b0;
    end
    else begin
      case (curr_state)
        idel: begin
          wb_stb_i  <= 1'b0;
          wb_addr_i <= 5'd0;
          wb_dat_i  <= 32'd0;
          wb_sel_i  <= 4'd0;
          wb_we_i   <= 1'b0;
          wb_cyc_i  <= 1'b0;
        end

        clear_ss: begin
          wb_addr_i <= 5'h18;
          wb_dat_i  <= 32'd0;
          wb_sel_i  <= 4'b0001;
          wb_we_i   <= 1'b1;
          if (wb_stb_i && wb_ack_o) begin
            wb_stb_i <= 1'b0;
            wb_cyc_i <= 1'b0;
          end
          else begin
            wb_stb_i <= 1'b1;
            wb_cyc_i <= 1'b1;
          end
        end

        set_divider: begin
          wb_addr_i <= 5'h14;
          wb_dat_i  <= 32'd1;
          wb_sel_i  <= 4'b0011;
          wb_we_i   <= 1'b1;
          if (wb_stb_i && wb_ack_o) begin
            wb_stb_i <= 1'b0;
            wb_cyc_i <= 1'b0;
          end
          else begin
            wb_stb_i <= 1'b1;
            wb_cyc_i <= 1'b1;
          end
        end

        set_ss: begin
          wb_addr_i <= 5'h18;
          wb_dat_i  <= 32'd1;
          wb_sel_i  <= 4'b0001;
          wb_we_i   <= 1'b1;
          if (wb_stb_i && wb_ack_o) begin
            wb_stb_i <= 1'b0;
            wb_cyc_i <= 1'b0;
          end
          else begin
            wb_stb_i <= 1'b1;
            wb_cyc_i <= 1'b1;
          end
        end

        set_CHAR_LEN: begin
          wb_addr_i <= 5'h10;
          wb_dat_i  <= 32'h40;
          wb_sel_i  <= 4'b0001;
          wb_we_i   <= 1'b1;
          if (wb_stb_i && wb_ack_o) begin
            wb_stb_i <= 1'b0;
            wb_cyc_i <= 1'b0;
          end
          else begin
            wb_stb_i <= 1'b1;
            wb_cyc_i <= 1'b1;
          end
        end

        set_addr: begin
          wb_addr_i <= 5'h04;
          wb_dat_i  <= 32'h03000000 + (in_paddr - flash_addr_start);
          wb_sel_i  <= 4'b1111;
          wb_we_i   <= 1'b1;
          if (wb_stb_i && wb_ack_o) begin
            wb_stb_i <= 1'b0;
            wb_cyc_i <= 1'b0;
          end
          else begin
            wb_stb_i <= 1'b1;
            wb_cyc_i <= 1'b1;
          end
        end

        set_start: begin
          wb_addr_i <= 5'h10;
          wb_dat_i  <= 32'h1100;
          wb_sel_i  <= 4'b0010;
          wb_we_i   <= 1'b1;
          if (wb_stb_i && wb_ack_o) begin
            wb_stb_i <= 1'b0;
            wb_cyc_i <= 1'b0;
          end
          else begin
            wb_stb_i <= 1'b1;
            wb_cyc_i <= 1'b1;
          end
        end

        wait_end: begin
          wb_addr_i <= 5'h10;
          wb_dat_i  <= 32'd0;
          wb_sel_i  <= 4'b0001;
          wb_we_i   <= 1'b0;
          wb_stb_i <= 1'b0;
          wb_cyc_i <= 1'b0;
          // if (wb_stb_i && wb_ack_o) begin
          //   wb_stb_i <= 1'b0;
          //   wb_cyc_i <= 1'b0;
          // end
          // else begin
          //   wb_stb_i <= 1'b1;
          //   wb_cyc_i <= 1'b1;
          // end
        end

        read_data: begin
          wb_addr_i <= 5'h00;
          wb_dat_i  <= 32'd0;
          wb_sel_i  <= in_pstrb;
          wb_we_i   <= 1'b0;
          if (wb_stb_i && wb_ack_o) begin
            wb_stb_i <= 1'b0;
            wb_cyc_i <= 1'b0;
          end
          else begin
            wb_stb_i <= 1'b1;
            wb_cyc_i <= 1'b1;
          end
        end

        default: begin
          wb_stb_i  <= 1'b0;
          wb_addr_i <= 5'd0;
          wb_dat_i  <= 32'd0;
          wb_sel_i  <= 4'd0;
          wb_we_i   <= 1'b0;
          wb_cyc_i  <= 1'b0;
        end
      endcase
    end
  end

always @(*) begin
  if (in_paddr >= 32'h1000_1000 && in_paddr <= 32'h1000_1fff) begin
    stb_i      = in_psel;
    addr_i     = in_paddr[4:0];
    dat_i      = in_pwdata;
    sel_i      = in_pstrb;
    we_i       = in_pwrite;
    cyc_i      = in_penable;
    in_pready  = wb_ack_o;
    in_pslverr = wb_err_o;
    in_prdata  = wb_dat_o;
  end
  else if (in_paddr >= flash_addr_start && in_paddr <= flash_addr_end) begin
    stb_i  = wb_stb_i;
    addr_i = wb_addr_i;
    dat_i  = wb_dat_i;
    sel_i  = wb_sel_i;
    we_i   = wb_we_i;
    cyc_i  = wb_cyc_i;
    in_prdata  = { wb_dat_o[7:0], wb_dat_o[15:8], wb_dat_o[23:16], wb_dat_o[31:24] };
    in_pslverr = wb_err_o;
    if (curr_state == read_data && next_state == idel) begin
      in_pready = 1'b1;
    end
    else begin
      in_pready = 1'b0;
    end
  end
  else begin
    stb_i      = 1'b0;
    addr_i     = 5'd0;
    dat_i      = 32'd0;
    sel_i      = 4'd0;
    we_i       = 1'b0;
    cyc_i      = 1'b0;
    in_pready  = 1'b0;
    in_pslverr = wb_err_o;
    in_prdata  = wb_dat_o;
  end
end

spi_top u0_spi_top (
  .wb_clk_i(clock),
  .wb_rst_i(reset),
  .wb_adr_i(addr_i),
  .wb_dat_i(dat_i),
  .wb_dat_o(wb_dat_o),
  .wb_sel_i(sel_i),
  .wb_we_i (we_i),
  .wb_stb_i(stb_i),
  .wb_cyc_i(cyc_i),
  .wb_ack_o(wb_ack_o),
  .wb_err_o(wb_err_o),
  .wb_int_o(spi_irq_out),

  .ss_pad_o(spi_ss),
  .sclk_pad_o(spi_sck),
  .mosi_pad_o(spi_mosi),
  .miso_pad_i(spi_miso)
);

`endif // FAST_FLASH

endmodule
